package cn.tannn.cat.file.sdk.core.minio;

import cn.tannn.cat.file.sdk.bean.FileIndex;
import cn.tannn.cat.file.sdk.bean.FileStorage;
import cn.tannn.cat.file.sdk.config.MinioConfig;
import cn.tannn.cat.file.sdk.core.OssCache;
import cn.tannn.cat.file.sdk.exception.FileException;
import cn.tannn.cat.file.sdk.utils.FileUtils;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import lombok.extern.slf4j.Slf4j;

import java.io.InputStream;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * minio工具类
 *
 * @author <a href="https://tannn.cn/">tan</a>
 * @date 2024/3/20 15:27
 */
@Slf4j
public class MinioUtils {


    /**
     * 创建minio client cache
     *
     * @param fileStorage Minio{@link FileStorage}
     */
    public static void createMinioClient(FileStorage fileStorage) {
        try {
            MinioConfig minio = fileStorage.getConfig().toJavaObject(MinioConfig.class);
            if (Objects.nonNull(minio)) {
                MinioClient minioClient = createMinioClient(minio);
                // ID 是唯一的
                OssCache.minioClientCache.put(fileStorage.getId(), minioClient);
            }
        } catch (Exception e) {
            log.error("minio《" + fileStorage.getName() + "》配置异常，请检查", e);
        }
    }

    /**
     * 创建minio client cache
     *
     * @param minio MinioConfig
     * @return MinioClient
     */
    public static MinioClient createMinioClient(MinioConfig minio) {
        MinioClient minioClient;
        if (minio.getPort() == null) {
            minioClient = MinioClient.builder().endpoint(minio.getUploadUrl())
                    .credentials(minio.getAccessKey(),
                            minio.getSecretKey()).build();
        } else {
            minioClient = MinioClient.builder().endpoint(minio.getUploadUrl(),
                            minio.getPort(),
                            minio.getHttps())
                    .credentials(minio.getAccessKey(), minio.getSecretKey())
                    .build();
        }
        return minioClient;
    }

    /**
     * 获取 minio 连接
     *
     * @param fileStorageId 存储器ID
     * @return MinioClient
     */
    public static MinioClient getMinioClient(Long fileStorageId) {
        MinioClient minioClient = OssCache.minioClientCache.get(fileStorageId);
        if (minioClient == null) {
            throw FileException.specialMessage("minio配置被删除，请重新添加");
        }
        return minioClient;
    }


    /**
     * 删除minio client cache
     *
     * @param fileStorageId 存储器ID
     * @return MinioClient
     */
    public static void deleteMinioClient(Long fileStorageId) {
        try {
            OssCache.minioClientCache.remove(fileStorageId);
        } catch (Exception e) {
            log.error("删除minio client cache 失败 fileStorageId {}", fileStorageId, e);
        }
    }


    /**
     * 检查存储桶是否存在
     *
     * @param fileStorageId 存储器ID
     * @param bucket       存储桶名称
     * @throws Exception 异常
     */
    public static boolean bucketExists(Long fileStorageId, String bucket) throws Exception {
        MinioClient minioClient = getMinioClient(fileStorageId);
        return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucket).build());
    }


    /**
     * 创建一个新的存储桶 [默认为公开桶]
     *
     * @param fileStorageId 存储器ID
     * @param bucket       存储桶名称（文件存放最外层的根目录）
     */
    public static void makeBucket(Long fileStorageId, String bucket) {
        try {
            // 如存储桶不存在，创建之。
            boolean found = bucketExists(fileStorageId, bucket);
            if (!found) {
                MinioClient minioClient = getMinioClient(fileStorageId);
                // 创建名为'bucket'的存储桶。
                minioClient.makeBucket(
                        MakeBucketArgs.builder()
                                .bucket(bucket)
                                .build());
                String str = " {\n" +
                        "     \"Statement\": [\n" +
                        "         {\n" +
                        "             \"Action\": [\n" +
                        "                 \"s3:GetBucketLocation\",\n" +
                        "                 \"s3:ListBucket\"\n" +
                        "             ],\n" +
                        "             \"Effect\": \"Allow\",\n" +
                        "             \"Principal\": \"*\",\n" +
                        "             \"Resource\": \"arn:aws:s3:::" + bucket + "\"\n" +
                        "         },\n" +
                        "         {\n" +
                        "             \"Action\": \"s3:GetObject\",\n" +
                        "             \"Effect\": \"Allow\",\n" +
                        "             \"Principal\": \"*\",\n" +
                        "             \"Resource\": \"arn:aws:s3:::" + bucket + "/*\"\n" +
                        "         }\n" +
                        "     ],\n" +
                        "     \"Version\": \"2012-10-17\"\n" +
                        " }";
                minioClient.setBucketPolicy(
                        SetBucketPolicyArgs.builder().bucket(bucket).config(str).build());
            }
        } catch (Exception e) {
            throw FileException.specialMessage("minio桶创建失败，请检测配置账户时候有权限! ", e);
        }
    }


    /**
     * 列出所有存储桶
     *
     * @param fileStorageId 存储器ID
     * @return 桶集合
     * @throws Exception 异常
     */
    public static List<Bucket> listBuckets(Long fileStorageId) throws Exception {
        MinioClient minioClient = getMinioClient(fileStorageId);
        // 列出所有存储桶
        return minioClient.listBuckets();
    }


    /**
     * 删除桶
     *
     * @param fileStorageId 存储器ID
     * @param bucketName   桶名称，桶中不能有对象，否则报错
     */
    public static void removeBucket(Long fileStorageId, String bucketName) throws Exception {
        MinioClient minioClient = getMinioClient(fileStorageId);
        minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
    }


    /**
     * 获取对象信息和对象的元数据
     *
     * @param fileStorageId 存储器ID
     * @param bucket       桶
     * @param objectName   桶中的对象名称
     * @return StatObjectResponse
     */
    public static StatObjectResponse statObject(Long fileStorageId, String bucket, String objectName) throws Exception {
        boolean found = bucketExists(fileStorageId, bucket);
        if (!found) {
            throw FileException.specialMessage("桶不存在！");
        }
        return getMinioClient(fileStorageId).statObject(
                StatObjectArgs.builder()
                        .bucket(bucket)
                        .object(objectName)
                        .build());
    }


    /**
     * 获取对象的数据。 返回的InputStream必须在使用后关闭，以释放网络资源。
     *
     * @param fileStorageId 存储器ID
     * @param bucket       桶
     * @param objectName   桶中的对象名称 {@link FileIndex#getPath()}
     * @return InputStream
     */
    public static InputStream fileInputStream(Long fileStorageId, String bucket, String objectName) throws Exception {
        boolean found = bucketExists(fileStorageId, bucket);
        if (!found) {
            throw FileException.specialMessage("桶不存在！");
        }
        return getMinioClient(fileStorageId).getObject(
                GetObjectArgs.builder()
                        .bucket(bucket)
                        .object(objectName)
                        .build());
    }


    /**
     * 生成一个有效时间内的访问连接
     *
     * @param fileStorageId 存储器ID
     * @param bucket     桶
     * @param objectName 名称 {@link FileIndex#getPath()}
     * @param expires    过期时间 失效时间（以秒为单位），默认是7天
     */
    public static String getExpiryObjectUrl(Long fileStorageId, String bucket, String objectName, Integer expires) throws Exception {
        boolean found = bucketExists(fileStorageId, bucket);
        if (!found) {
            throw new RuntimeException("桶不存在！");
        }
        return getMinioClient(fileStorageId).getPresignedObjectUrl(
                GetPresignedObjectUrlArgs.builder()
                        .method(Method.GET)
                        .bucket(bucket)
                        .object(objectName)
                        .expiry(FileUtils.defExpires(expires), TimeUnit.SECONDS)
                        .build());
    }

}
